package gao.xiaolei.service;

import gao.xiaolei.dao.*;
import gao.xiaolei.entity.*;
import gao.xiaolei.exception.AuthException;
import gao.xiaolei.exception.LeafException;
import gao.xiaolei.exception.StatusException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import gao.xiaolei.dto.TestDesignInDto;
import gao.xiaolei.dto.TestDesignOutDto;
import gao.xiaolei.dto.TestRequirementDesignCoverDto;
import gao.xiaolei.dto.TestTreeDto;
import xiaolei.gao.file.FileInterface;
import gao.xiaolei.util.ServiceName;
import gao.xiaolei.util.UuidUtil;

import javax.annotation.Resource;

import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;

@Service("TestDesignService")
public class TestDesignService {

    @Resource(name = "TestDesignDao")
    private TestDesignDao testDesignDao;

    @Resource(name = "TestRunDao")
    private TestRunDao testRunDao;

    @Autowired
    private RestTemplate restTemplate;

    @Resource(name = "TestStatusDao")
    private TestStatusDao testStatusDao;

    @Resource(name = "TestStepDao")
    private TestStepDao testStepDao;

    @Resource(name = "TestRequirementDesignCoverDao")
    private TestRequirementDesignCoverDao testRequirementDesignCoverDao;

    @Resource(name = "FileInterface")
    private FileInterface fileInterface;

    @Resource(name = "TestRequirementDao")
    private TestRequirementDao testRequirementDao;

    @Resource(name = "AnalysisService")
    private AnalysisService analysisService;

    @Transactional
    //添加测试计划
    public void addTestDesign(TestDesignInDto testDesignInDto) {
        TestDesign testDesign = testDesignInDto.getTestDesign();
        if ("root".equals(testDesign.getParentId()) && testDesign.getLeaf() == 1)
            throw new LeafException("最顶端只能添加主题文件夹！");
        if (!"root".equals(testDesign.getParentId()) && testDesignDao.getLeaf(testDesign.getParentId()) == 1)//如果是添加的父节点本身就是叶子节点的话就报错
            throw new LeafException("添加的父节点为叶子节点！");
        LocalDateTime createDate = LocalDateTime.now(Clock.system(ZoneId.of("Asia/Shanghai")));
        testDesign.setCreateDate(createDate);
        testDesign.setUpdateDate(createDate);
        testDesignDao.save(testDesign);//如果添加的是主题文件夹,就不用添加测试步骤和关联测试需求,也不用拉去审核
        if (testDesign.getLeaf() == 1) {//反之则要做这些步骤
            Map<String, String> willUplodeFile = new HashMap<>();//防止数据库回退但是文件操作依旧执行
            List<TestStep> steps = testDesignInDto.getStep();
            for (TestStep s : steps) {
                willUplodeFile.put(s.getId(), s.getDescription());//先将测试步骤对应的html文本保存起来
                s.setTestDesign(testDesign.getId());
            }
            testStepDao.saveAll(testDesignInDto.getStep());
            List<String> trq = testDesignInDto.getTestReq();
            List<TestRequirementDesignCover> trqCover = new ArrayList<>(trq.size());
            for (String t : trq) {
                trqCover.add(new TestRequirementDesignCover(UuidUtil.get32Uuid(), t, testDesign.getId()));
            }
            testRequirementDesignCoverDao.saveAll(trqCover);
            for (Map.Entry<String, String> uf : willUplodeFile.entrySet()) {
                fileInterface.fileUpload(uf.getValue(), uf.getKey(), testDesign.getProject());//将每个测试步骤的理由都更新一下
            }
        }
    }

    // 删除测试计划
    @Transactional
    public void deleteTestDesign(String id, String userId) {
        if (!testDesignDao.getAuthor(id).orElse("").equals(userId))
            throw new AuthException("你无此权限删除！");
        if (testDesignDao.getStatus(id) == 2)
            throw new StatusException("审核中的节点不能删除");
        String project = testDesignDao.getProject(id);
        List<String> willDeletedFile = new LinkedList<>();//防止数据库回退但是文件操作依旧执行
        recurrenceDelete(id, willDeletedFile);//递归删除其所有的子节点，包括其自己
        analysisService.doAnalysis(testDesignDao.getProject(id));//更新测试需求分析
        for (String df : willDeletedFile) {
            fileInterface.fileDelete(df, project);
        }
    }

    //修改测试计划
    @Transactional
    public void updateTestDesign(TestDesignInDto testDesignInDto) {
        String userId = testDesignInDto.getUserId();
        TestDesign testDesign = testDesignInDto.getTestDesign();
        if (!testDesignDao.existsById(testDesign.getId()))
            throw new IllegalArgumentException("该测试计划不存在！");
        if (!testDesignDao.getAuthor(testDesign.getId()).orElse("").equals(userId))
            throw new AuthException("你无此权限修改！");
        if (testDesignDao.getStatus(testDesign.getId()) == 2)
            throw new StatusException("审核中的节点不能修改");
        LocalDateTime updateDate = LocalDateTime.now(Clock.system(ZoneId.of("Asia/Shanghai")));
        LocalDateTime createDate = testDesignDao.getCreateDate(testDesign.getId());
        testDesign.setUpdateDate(updateDate);
        testDesign.setCreateDate(createDate);
        testDesignDao.save(testDesign);
        if (testDesign.getLeaf() == 1) {//如果不是主题文件夹就要执行下面的操作
            List<TestStep> oldStep = testStepDao.findByTestDesign(testDesign.getId());
            List<TestStep> newStep = testDesignInDto.getStep();
            List<String> willDeletedFile = new LinkedList<>();//防止数据库回退但是文件操作依旧执行
            Map<String, String> willUplodeFile = new HashMap<>();//防止数据库回退但是文件操作依旧执行
            for (TestStep os : oldStep) {
                boolean flag = true;//判断这个旧测试步骤是否要删除
                for (TestStep ns : newStep) {
                    if (os.getId().equals(ns.getId())) {
                        flag = false;//新旧步骤里面都有的话，就不用删除
                        break;
                    }
                }
                if (flag) {//如果新步骤里没有就说明这个旧测试步骤要删除，所以要去文件服务删除
                    willDeletedFile.add(os.getId());
                    testStepDao.deleteById(os.getId());
                }
            }
            for (TestStep ns : newStep) {//保存新测试步骤
                willUplodeFile.put(ns.getId(), ns.getDescription());
                ns.setTestDesign(testDesign.getId());
                testStepDao.save(ns);
            }
            List<TestRequirementDesignCover> oldCovers = testRequirementDesignCoverDao.findByTestDesignId(testDesign.getId());
            List<String> newCovers = testDesignInDto.getTestReq();
            for (String nc : newCovers) {//将新测试需求覆盖多出来的保存掉
                boolean flag = true;//一开始假设要添加
                for (int i = 0, length = oldCovers.size(); i < length; i++) {
                    if (nc.equals(oldCovers.get(i).getTestRequirementId())) {
                        flag = false;//如果旧的里面有就不用添加
                        break;
                    }
                }
                if (flag)
                    testRequirementDesignCoverDao.save(new TestRequirementDesignCover(UUID.randomUUID().toString().replaceAll("-", ""), nc, testDesign.getId()));
            }
            for (TestRequirementDesignCover oc : oldCovers) {//将旧测试需求多出来的删除掉
                boolean flag = false;
                for (String nc : newCovers) {
                    if (nc.equals(oc.getTestRequirementId())) {
                        flag = true;
                        break;
                    }
                }
                if (!flag)
                    testRequirementDesignCoverDao.delete(oc);
            }
            for (String df : willDeletedFile) {
                fileInterface.fileDelete(df, testDesign.getProject());//删除不要的测试步骤
            }
            for (Map.Entry<String, String> uf : willUplodeFile.entrySet()) {
                fileInterface.fileUpload(uf.getValue(), uf.getKey(), testDesign.getProject());//添加新的测试步骤
            }
            analysisService.doAnalysis(testDesign.getProject());//更新测试需求分析
        }
    }

    //修改测试计划标题
    @Transactional
    public void updateTestDesignTitle(String id, String title, String userId) {
        int status = testDesignDao.getStatus(id);
        if (!testDesignDao.getAuthor(id).orElse("").equals(userId))
            throw new AuthException("你无此权限修改！");
        if (status == 2)
            throw new StatusException("审核中的节点不能修改");
        testDesignDao.updateTitle(id, title);
        testDesignDao.updateStatus(id, 0);
        analysisService.doAnalysis(testDesignDao.getProject(id));//更新测试需求分析
    }

    // 修改父节点
    @Transactional
    public void updateParentId(String id, String newParentId, String userId) {
        int status = testDesignDao.getStatus(id);
        if (!testDesignDao.getAuthor(id).orElse("").equals(userId))
            throw new AuthException("你无此权限移动！");
        if (status == 2)
            throw new StatusException("审核中的节点不能移出或移入！");
        if (testDesignDao.getLeaf(newParentId) == 1)
            throw new LeafException("只能移入文件夹！");
        testDesignDao.updateParentId(id, newParentId);
        testDesignDao.updateStatus(id, 0);
        analysisService.doAnalysis(testDesignDao.getProject(id));//更新测试需求分析
    }

    //根据项目和作者来查询所有最顶端主题文件夹(一个人只能看到所有主题文件夹以及自己写的和别人已经审核通过了的)
    @Transactional(readOnly = true)
    public List<TestTreeDto> getRootSubject(String projectId, String userId) {
        List<Object[]> os = testDesignDao.findByProjectAndParentIdOrderByTitle(projectId, "root");
        return getTestDesignOutDtos(os, userId, 0);
    }

    //根据项目查询所有最顶端主题文件夹(给测试执行提供关联测试计划的选择（只提供审核通过的测试计划）)
    @Transactional(readOnly = true)
    public List<TestTreeDto> getRootSubjectForTestRun(String projectId) {
        List<Object[]> os = testDesignDao.findByProjectAndParentIdOrderByTitle(projectId, "root");
        return getTestDesignOutDtos(os, null, 2);
    }

    //根据项目来查询所有最顶端主题文件夹(为了给测试需求转测试计划选择主题)
    @Transactional(readOnly = true)
    public List<TestTreeDto> getRootSubjectForTransform(String projectId) {
        List<Object[]> os = testDesignDao.findByProjectAndParentIdOrderByTitleForTransform(projectId, "root");
        return getTestDesignOutDtos(os, null, 1);
    }

    //获取某个节点下的所有能显示的节点(只有已经审核通过了的和文件夹)
    @Transactional(readOnly = true)
    public List<TestTreeDto> getChildrenIsPass(String id) {
        List<Object[]> os = testDesignDao.getChildrenIsPass(id);
        return getTestDesignOutDtos(os, null, 2);
    }

    //根据作者来获取某个节点的所有能显示的节点(一个人只能看到自己写的和别人已经审核通过了的)
    @Transactional(readOnly = true)
    public List<TestTreeDto> getChildren(String id, String userId) {
        List<Object[]> os = testDesignDao.getChildren(id, userId);
        return getTestDesignOutDtos(os, userId, 0);
    }

    //获取某个节点下的所有主题文件夹
    @Transactional(readOnly = true)
    public List<TestTreeDto> getChildrenIsSubject(String id) {
        List<Object[]> os = testDesignDao.getChildrenIsFolder(id);
        return getTestDesignOutDtos(os, null, 1);
    }

    // 获取该测试计划的的详细信息
    @Transactional(readOnly = true)
    public TestDesignOutDto getDetail(String id) {
        TestDesign testDesign = testDesignDao.findById(id).orElseThrow(() -> new IllegalArgumentException("该测试计划不存在！"));
        String author = restTemplate.getForObject(ServiceName.AUTH_SERVICE.getServiceName().concat("/user/realName/").concat(testDesign.getAuthor()), String.class);
        if (testDesign.getLeaf() == 0)//如果是主题文件夹就只要获取描述信息和作者就好了
            return new TestDesignOutDto(testDesign.getTitle(), author, testDesign.getAuthor(), testDesign.getDescribe(), testDesign.getCreateDate(), testDesign.getUpdateDate(), null);
        //不然就要把测试步骤、关联的测试需求、审核记录给全部拿出来
        String status = testStatusDao.getDescribeOfCode(testDesign.getStatus());
        List<TestStep> steps = testStepDao.findByTestDesign(id);
        for (int i = 0, length = steps.size(); i < length; i++) {//将测试步骤的文件内容获取出来
            byte[] textByte = fileInterface.getFile(steps.get(i).getId(), testDesign.getProject());
            steps.get(i).setDescription(new String(textByte, StandardCharsets.UTF_8));
        }
        List<TestRequirementDesignCover> covers = testRequirementDesignCoverDao.findByTestDesignId(id);
        List<TestRequirementDesignCoverDto> coverDtos = new LinkedList<>();
        for (TestRequirementDesignCover c : covers) {
            if (testRequirementDao.getStatus(c.getTestRequirementId()) == 3) {//只是获取通过审核的覆盖测试需求
                TestRequirement req = testRequirementDao.findById(c.getTestRequirementId()).orElseThrow(() -> new IllegalArgumentException("该需求不存在！"));
                String reqAuthor = restTemplate.getForObject(ServiceName.AUTH_SERVICE.getServiceName().concat("/user/realName/").concat(req.getAuthor()), String.class);
                byte[] textByte = fileInterface.getFile(req.getId(), req.getProject());
                coverDtos.add(new TestRequirementDesignCoverDto(req.getId(), getTrqPath(req.getId()), reqAuthor, new String(textByte, StandardCharsets.UTF_8)));
            }
        }
        return new TestDesignOutDto(testDesign.getId(), testDesign.getTitle(), author, testDesign.getAuthor(), testDesign.getDescribe(), status, testDesign.getPriority(), testDesign.getCreateDate(), testDesign.getUpdateDate(), steps, coverDtos);
    }

    @Transactional(readOnly = true)
    //用于给测试执行获取测试计划的步骤内容
    public List<TestStep> getStep(String testDesignId) {
        if (!testDesignDao.existsById(testDesignId))
            throw new IllegalArgumentException("该测试计划不存在！");
        if (testDesignDao.getStatus(testDesignId) == 2)
            throw new StatusException("审核中的计划不能使用！");
        List<TestStep> steps = testStepDao.findByTestDesign(testDesignId);
        for (int i = 0, length = steps.size(); i < length; i++) {
            byte[] fileByte = fileInterface.getFile(steps.get(i).getId(), testDesignDao.getProject(testDesignId));
            steps.get(i).setDescription(new String(fileByte, StandardCharsets.UTF_8));
        }
        return steps;
    }

    //将对应的测试计划转化成测试执行
    @Transactional
    public void transformDesignToRun(String designId, String name, String testLab, int priority, String author) {
        TestDesign testDesign = testDesignDao.findById(designId).orElseThrow(() -> new IllegalArgumentException("该测试执行不存在！"));
        if (testDesign.getStatus() != 3)
            throw new StatusException("只能转换通过审核的节点");
        if (testDesign.getLeaf() != 1)
            throw new LeafException("只能转换非主题文件夹！");
        String runId = UuidUtil.get32Uuid();
        LocalDateTime createDate = LocalDateTime.now(Clock.system(ZoneId.of("Asia/Shanghai")));
        String describe = "由测试计划(".concat(testDesign.getTitle()).concat("）转换而来的测试执行");
        testRunDao.save(new TestRun(runId, name, author, describe, testDesign.getProject(), 0, priority, 1, testLab, designId, createDate, createDate));
    }

    //获取对应测试计划的标题
    public String getTitle(String id) {
        return testDesignDao.getTitle(id);
    }


    //将List<Object[]>转换成List<TestTreeDto>
    private List<TestTreeDto> getTestDesignOutDtos(List<Object[]> os, String userId, int type) {
        List<TestTreeDto> designOutDtos = new ArrayList<>(os.size());
        for (int i = 0, length = os.size(); i < length; i++) {
            int count = 0;
            if ((Integer) os.get(i)[3] == 0) {//如果该节点不是叶子节点的话
                if (type == 0)//供测试计划自己的树形菜单使用
                    count = testDesignDao.getChildrenCount((String) os.get(i)[0], userId);//获取这个节点下能够显示的节点数量
                else if (type == 1)///供测试需求转测试计划用
                    count = testDesignDao.getChildrenCountIsFolder((String) os.get(i)[0]);
                else//供测试执行选择测试计划用
                    count = testDesignDao.getChildrenCountIsPass((String) os.get(i)[0]);//获取这个节点下能够显示的节点数量
            }
            designOutDtos.add(new TestTreeDto((String) os.get(i)[0], (String) os.get(i)[1], (String) os.get(i)[2], (Integer) os.get(i)[3], type > 0 ? null : (String) os.get(i)[4], count, type == 0 ? (Integer) os.get(i)[5] : null));
        }
        return designOutDtos;
    }

    //用于递归删除子节点的方法
    private void recurrenceDelete(String id, List<String> willDeleteFile) {
        if (testDesignDao.getLeaf(id) == 0) {//如果不是叶子节点则遍历它的所有子节点来递归删除
            List<String> cls = testDesignDao.getChildrenId(id);
            for (int i = 0, length = cls.size(); i < length; i++) {
                recurrenceDelete(cls.get(i), willDeleteFile);
            }
        }
        testDesignDao.deleteById(id);//最后一定要把自己删除
        testStepDao.deleteByTestDesign(id);//删除对应的测试步骤
        willDeleteFile.add(id);
        testRequirementDesignCoverDao.deleteByTestDesignId(id);//删除对应的测试需求覆盖
    }

    //获取某一个测试需求的路径
    private String getTrqPath(String trqId) {
        List<String> path = new LinkedList<>();
        String parentId = testRequirementDao.getParentId(trqId);
        while (!"root".equals(parentId) && parentId != null) {
            path.add(testRequirementDao.getTitle(parentId));
            parentId = testRequirementDao.getParentId(parentId);
        }
        StringBuilder result = new StringBuilder();
        for (int i = path.size() - 1; i >= 0; i--) {
            result.append(path.get(i)).append("/");
        }
        result.append(testRequirementDao.getTitle(trqId));
        return result.toString();
    }

}
